#pragma once

#include <stdexcept>

#include <map>
#include <string>
#include <vector>

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/repository/include/qi_confix.hpp>

#include <boost/fusion/include/std_pair.hpp>

#include <iostream>

namespace qi = boost::spirit::qi;

struct config_parser_traits {

    static const char* comment_s;
    static const char* map_entry_sep;
    static const char end_of_expr_ch;
    static const char eq_ch;  
    static const char map_entry_key_sep;
};

template <typename Iterator, typename Traits = config_parser_traits>
struct skip_grammar : public qi::grammar<Iterator> {

    skip_grammar() : skip_grammar::base_type(skip) {

        skip = qi::space  
             | boost::spirit::repository::confix(Traits::comment_s, qi::eol) 
               [*(qi::char_ - qi::eol)]; 
    }

    qi::rule<Iterator> skip;
};

typedef std::map<std::string, std::string> VariablesMapType;
typedef std::pair<std::vector<std::string>, std::string> MapEntryType;
typedef std::vector<MapEntryType> MapEntriesType;
typedef std::pair<VariablesMapType, MapEntriesType> ParserResultType;

template <typename Iterator, typename Traits = config_parser_traits>
class config_parser_grammar : public qi::grammar<Iterator,
                                     ParserResultType(),
                                     skip_grammar<Iterator, Traits>> {
public:

    config_parser_grammar() : config_parser_grammar::base_type(config_data) {

        config_data = pairs >> map_entries;

        pairs = pair % Traits::end_of_expr_ch >> 
                Traits::end_of_expr_ch; 

        key = (qi::alpha | qi::char_('_')) >> *(qi::alnum | qi::char_('_'));

        map_key = *(qi::char_ - Traits::map_entry_key_sep - 
                  Traits::map_entry_sep[0] - Traits::map_entry_sep[1]);

        pair = (int_p | double_p | size_t_p);

        int_value = +qi::digit;
        double_value = int_value >> -(qi::char_('.') >> int_value);
        string_value = +qi::alpha;

        int_p = qi::lexeme["int:"] >> key >> Traits::eq_ch >> 
                &qi::int_ >> int_value;

        double_p = qi::lexeme["double:"] >> key >> Traits::eq_ch >> 
                   &qi::double_ >> double_value;

        size_t_p = qi::lexeme["size_t:"] >> key >> Traits::eq_ch >> 
                   &qi::uint_ >> int_value; 

        map_entry = Traits::map_entry_sep[0] >> 
                    map_key % Traits::map_entry_key_sep >>
                    Traits::map_entry_sep[1] >>
                    Traits::eq_ch >>
                    string_value;        

        map_entries = map_entry % Traits::end_of_expr_ch >>
                      Traits::end_of_expr_ch;

    }

    qi::rule<Iterator,
             ParserResultType(),
             skip_grammar<Iterator, Traits>> config_data;

    qi::rule<Iterator, 
             VariablesMapType(), 
             skip_grammar<Iterator, Traits>> pairs;

    qi::rule<Iterator, 
             std::pair<std::string, std::string>(),
             skip_grammar<Iterator, Traits>> pair, int_p, double_p, size_t_p;

    qi::rule<Iterator,
             std::string()> key, map_key, int_value, double_value, string_value;

    qi::rule<Iterator,
             MapEntriesType(),
             skip_grammar<Iterator, Traits>> map_entries;

    qi::rule<Iterator,
             MapEntryType(),
             skip_grammar<Iterator, Traits>> map_entry;
};

template <typename T, typename Traits = config_parser_traits>
class ConfigParserT {
public:

    bool parse(T& input, VariablesMapType& variables, 
               std::map<std::string, std::string>& map_entries) {

        config_parser_grammar<typename T::iterator, Traits> p_grammar;
        skip_grammar<typename T::iterator, Traits> sk_grammar;    
        typename T::iterator begin = input.begin();
        
        ParserResultType res;
        bool good = boost::spirit::qi::phrase_parse(begin, input.end(), p_grammar, sk_grammar, res);
        if(!good || begin != input.end()) {
            return false;
        }

        variables = res.first;
        map_entries = convert_to_map(res.second);

        return true;
    }  

private:

    std::map<std::string, std::string> convert_to_map(
                                       const MapEntriesType& map_entries) const {
        
        std::map<std::string, std::string> result;
        auto end = map_entries.end();
        for(auto iter = map_entries.begin(); iter != end; ++iter) {
            auto vec = iter->first;
            auto val = iter->second;
            for(auto in_iter = vec.begin(); in_iter != vec.end(); ++in_iter) {
                result[*in_iter] = val;
            }
        }
        return result;
    }
};

class ConfigParser {
public:

    bool parse(std::string& input, VariablesMapType& variables, 
               std::map<std::string, std::string>& formats); 
};








